/*
 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.io;


/**
 * Writes text to a character-output stream, buffering characters so as to
 * provide for the efficient writing of single characters, arrays, and strings.
 *
 * <p> The buffer size may be specified, or the default size may be accepted.
 * The default is large enough for most purposes.
 *
 * <p> A newLine() method is provided, which uses the platform's own notion of
 * line separator as defined by the system property <tt>line.separator</tt>.
 * Not all platforms use the newline character ('\n') to terminate lines.
 * Calling this method to terminate each output line is therefore preferred to
 * writing a newline character directly.
 *
 * <p> In general, a Writer sends its output immediately to the underlying
 * character or byte stream.  Unless prompt output is required, it is advisable
 * to wrap a BufferedWriter around any Writer whose write() operations may be
 * costly, such as FileWriters and OutputStreamWriters.  For example,
 *
 * <pre>
 * PrintWriter out
 *   = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));
 * </pre>
 *
 * will buffer the PrintWriter's output to the file.  Without buffering, each
 * invocation of a print() method would cause characters to be converted into
 * bytes that would then be written immediately to the file, which can be very
 * inefficient.
 *
 * @author Mark Reinhold
 * @see PrintWriter
 * @see FileWriter
 * @see OutputStreamWriter
 * @see java.nio.file.Files#newBufferedWriter
 * @since JDK1.1
 */

public class BufferedWriter extends Writer {

  private Writer out;

  private char cb[];
  private int nChars, nextChar;

  private static int defaultCharBufferSize = 8192;

  /**
   * Line separator string.  This is the value of the line.separator
   * property at the moment that the stream was created.
   */
  private String lineSeparator;

  /**
   * Creates a buffered character-output stream that uses a default-sized
   * output buffer.
   *
   * @param out A Writer
   */
  public BufferedWriter(Writer out) {
    this(out, defaultCharBufferSize);
  }

  /**
   * Creates a new buffered character-output stream that uses an output
   * buffer of the given size.
   *
   * @param out A Writer
   * @param sz Output-buffer size, a positive integer
   * @throws IllegalArgumentException If {@code sz <= 0}
   */
  public BufferedWriter(Writer out, int sz) {
    super(out);
    if (sz <= 0) {
      throw new IllegalArgumentException("Buffer size <= 0");
    }
    this.out = out;
    cb = new char[sz];
    nChars = sz;
    nextChar = 0;

    lineSeparator = java.security.AccessController.doPrivileged(
        new sun.security.action.GetPropertyAction("line.separator"));
  }

  /**
   * Checks to make sure that the stream has not been closed
   */
  private void ensureOpen() throws IOException {
    if (out == null) {
      throw new IOException("Stream closed");
    }
  }

  /**
   * Flushes the output buffer to the underlying character stream, without
   * flushing the stream itself.  This method is non-private only so that it
   * may be invoked by PrintStream.
   */
  void flushBuffer() throws IOException {
    synchronized (lock) {
      ensureOpen();
      if (nextChar == 0) {
        return;
      }
      out.write(cb, 0, nextChar);
      nextChar = 0;
    }
  }

  /**
   * Writes a single character.
   *
   * @throws IOException If an I/O error occurs
   */
  public void write(int c) throws IOException {
    synchronized (lock) {
      ensureOpen();
      if (nextChar >= nChars) {
        flushBuffer();
      }
      cb[nextChar++] = (char) c;
    }
  }

  /**
   * Our own little min method, to avoid loading java.lang.Math if we've run
   * out of file descriptors and we're trying to print a stack trace.
   */
  private int min(int a, int b) {
    if (a < b) {
      return a;
    }
    return b;
  }

  /**
   * Writes a portion of an array of characters.
   *
   * <p> Ordinarily this method stores characters from the given array into
   * this stream's buffer, flushing the buffer to the underlying stream as
   * needed.  If the requested length is at least as large as the buffer,
   * however, then this method will flush the buffer and write the characters
   * directly to the underlying stream.  Thus redundant
   * <code>BufferedWriter</code>s will not copy data unnecessarily.
   *
   * @param cbuf A character array
   * @param off Offset from which to start reading characters
   * @param len Number of characters to write
   * @throws IOException If an I/O error occurs
   */
  public void write(char cbuf[], int off, int len) throws IOException {
    synchronized (lock) {
      ensureOpen();
      if ((off < 0) || (off > cbuf.length) || (len < 0) ||
          ((off + len) > cbuf.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
      } else if (len == 0) {
        return;
      }

      if (len >= nChars) {
                /* If the request length exceeds the size of the output buffer,
                   flush the buffer and then write the data directly.  In this
                   way buffered streams will cascade harmlessly. */
        flushBuffer();
        out.write(cbuf, off, len);
        return;
      }

      int b = off, t = off + len;
      while (b < t) {
        int d = min(nChars - nextChar, t - b);
        System.arraycopy(cbuf, b, cb, nextChar, d);
        b += d;
        nextChar += d;
        if (nextChar >= nChars) {
          flushBuffer();
        }
      }
    }
  }

  /**
   * Writes a portion of a String.
   *
   * <p> If the value of the <tt>len</tt> parameter is negative then no
   * characters are written.  This is contrary to the specification of this
   * method in the {@linkplain java.io.Writer#write(java.lang.String, int, int)
   * superclass}, which requires that an {@link IndexOutOfBoundsException} be
   * thrown.
   *
   * @param s String to be written
   * @param off Offset from which to start reading characters
   * @param len Number of characters to be written
   * @throws IOException If an I/O error occurs
   */
  public void write(String s, int off, int len) throws IOException {
    synchronized (lock) {
      ensureOpen();

      int b = off, t = off + len;
      while (b < t) {
        int d = min(nChars - nextChar, t - b);
        s.getChars(b, b + d, cb, nextChar);
        b += d;
        nextChar += d;
        if (nextChar >= nChars) {
          flushBuffer();
        }
      }
    }
  }

  /**
   * Writes a line separator.  The line separator string is defined by the
   * system property <tt>line.separator</tt>, and is not necessarily a single
   * newline ('\n') character.
   *
   * @throws IOException If an I/O error occurs
   */
  public void newLine() throws IOException {
    write(lineSeparator);
  }

  /**
   * Flushes the stream.
   *
   * @throws IOException If an I/O error occurs
   */
  public void flush() throws IOException {
    synchronized (lock) {
      flushBuffer();
      out.flush();
    }
  }

  @SuppressWarnings("try")
  public void close() throws IOException {
    synchronized (lock) {
      if (out == null) {
        return;
      }
      try (Writer w = out) {
        flushBuffer();
      } finally {
        out = null;
        cb = null;
      }
    }
  }
}
